import time
import types

from exceptions import Exception
from activist.Data import Data
from activist.Config import Config
import activist.Time

from twisted.internet import defer

class TypeStringFudger:
    """this is so i can control how classes (instead of
    just instances) are printed out"""
    
    class FudgerType(type):
        def __str__(self):
            return self._str
        
    def __init__(self,output):
        self._output = output
    def __call__(self,a,b,c):
        rtn = TypeStringFudger.FudgerType(a,b,c)
        rtn._str = self._output
        return rtn


def relationshipFromName(rel):
    """jesus; need a better way to do this..."""
    rel = str(rel)
    if rel[:18] == 'activist.MetaData.':
        return eval( rel[9:]+"()" )

    if rel == 'DC.Creator':
        return MetaData.Creator()
    elif rel == 'DC.Author':
        return MetaData.Author()
    elif rel == 'DC.Title':
        return MetaData.Title()
    elif rel == 'DC.Relation(belongs-with)':
        return MetaData.BelongsWith()
    elif rel == 'DC.Relation(information-of)':
        return MetaData.InformationOf()
    elif rel == 'DC.Relation(located-at)':
        return MetaData.LocatedAt()
    elif rel == 'DC.Relation(comment-on)':
        return MetaData.CommentOn()
    elif rel == 'DC.Relation(message)':
        return MetaData.Message()

    print "no relationship for:",rel
    return MetaData.Relationship()

class Later:
    def __init__(self,callable,*args,**kwargs):
        self.__args = args
        self.__kwargs = kwargs
        self.__callable = callable
    def __call__(self,result):
        args = []
        for x in self.__args:
            if isinstance(x,defer.Deferred):
                args.append(x.result)
            else:
                args.append(x)
        kwargs = {}
        for (key,value) in self.__kwargs:
            if isinstance(value,defer.Deferred):
                kwargs[key] = value.result
            else:
                kwargs[key] = value
        print "calling",self.__callable,args,kwargs
        self.__callable(*tuple(args),**kwargs)


def _makemeta(*args,**kwargs):
    r = MetaData(*args,**kwargs)
    return r

def MetaDataFactory(*args,**kwargs):
    """
    WARNING: I must only be called from inside a command of some sort
    (otherwise, the metadata won't be serialized; MetaData isn't
    serialized, only the commands which produce it).
    """
    
    from activist.CommandProcessor import CommandProcessor
    deferred = []
    for arg in args:
        if isinstance(arg,defer.Deferred):
            deferred.append(arg)
    if len(deferred):
        dl = defer.DeferredList(deferred)
        return dl.addCallback(Later(_makemeta,*args,**kwargs))
    return _makemeta(*args,**kwargs)
            

class MetaData(Data):
    """
    metadata is data about data; it links two pieces of data with a
    relationship. we are using something like this in active2; it was
    mostly inspired by RDF, which uses 3-tuples for assertions about
    things: (thingA, relationship, thingB)

    in RDF, one of the data bits can be what they call a Literal,
    which is just a string. That probably makes sense for this system,
    to, so perhaps "datato" can be a string (or maybe any python
    type).

    One way to do versioning would be to have a "container" object for
    listings (say), so that a "listing" doesn't really have any of the
    listing data itself. instead, one would associate listing data via
    MetaData (i.e. MetaData(listing,InformationOf,thedata)) and hence
    the newest such assertion is the newest version... [i've done this
    in my class sketch]

    one could add a "creator" attribute to MetaData and then one would
    know who made changes.

    """

    def __repr__(self):
        return "<MetaData %s>" % str(self.id())

    def id(self):
        """
        overriding Data so that all MetaData is in a separate "id-space"
        """
        if not hasattr(self,"_hash"):
            from activist.CommandProcessor import CommandProcessor
            self._hash = CommandProcessor.generateNewMetaId()
        return self._hash

    
    def __init__(self, datafrom, relationship, datato, thetime=None ):
        """
        WARNING: metadata is a little different from the other data;
        it is created via other Data's .create() commands (or other
        commands) and hence doesn't need to be serialized (since those
        commands are serialized).
        """
        super(MetaData,self).__init__()
        self._createcalled = True       # there is no command_create for metadata
        self.command("settime")

        if not isinstance(datafrom,Data):
            raise "from data must be a Data, not:",datafrom
        if not isinstance(datato,Data):
            raise "to data must be a Data, not:",datato
        
        if thetime is None:
            thetime = time.time()
        
        if type(relationship) is types.StringType:
            relationship = relationshipFromName(relationship)

        if relationship and not isinstance(relationship,MetaData.Relationship) and not issubclass(relationship,MetaData.Relationship):
                raise Exception( 'createMetaData(): invalid relationship:'+ str(relationship) + str(type(relationship)) + str(dir(relationship)) )

        self._from = datafrom
        self._relationship = relationship
        self._to = datato
        self.settime(thetime)           # must do this so it gets serialized as a command

        if isinstance(self._from,Data):
            self._from.addMetaData(self)
        if isinstance(self._to,Data):
            self._to.addMetaData(self)

        if Config.debug: print "  META:",self


    command_settime_VERSION = 0
    def command_settime(self,tm):
        if 0:
            diff = time.time() - tm
            if diff < 1.0: diff = "now-ish"
            print "SETTIME",self.id(),tm,diff
        self._time = tm
        return tm

    def _remove(self):
        #print "  REMOVING metadata:",self,"from",self._from,self._to
        self._from.removeMetaData(self)
        self._to.removeMetaData(self)
        if 0:
            print "   TO metadata is now:"
            for x in self._to.metadata():
                print "     ",x
        Data._remove(self)

    def getXML(self):
        return """
<metadata class="activist.MetaData" id="%(id)s">
  <from>%(from)s</from>
  <to>%(to)s</to>
  <relationship>%(relationship)s</relationship>
  <time>%(time)s</time>
%(meta)s
</metadata>""" % { "id": self.id(),
                   "from": self.fromData().id(),
                   "kindfrom": str(self.fromData().__class__),
                   "to": self.toData().id(),
                   "time": self._time,
                   "relationship": str(self._relationship),
                   "meta":self.getMetaXML() # yes, even metadata has metadata
                   }


    def _postRestore(self):
        from activist.DataStore import DataStore
        self._from = DataStore[self._from]
        self._to = DataStore[self._to]
        
        if self._from:
            self._from.addMetaData(self)
        if self._to:
            self._to.addMetaData(self)
        
        print "postrestore",self

    def __str__(self):
        if not isinstance(self._from,Data):
            return "<MetaData: PENDING:%s,%s,PENDING:%s >" % (self._from,self._relationship,self._to)
        return "<MetaData: %s,%s,%s >" % (self._from.id(),self._relationship,self._to.id())

    def __repr__(self):
        if not isinstance(self._from,Data):
            return "<MetaData: PENDING:%s,%s,PENDING:%s >" % (self._from,self._relationship,self._to)
        return "<MetaData: %s,%s,%s >" % (self._from.id(),self._relationship,self._to.id())

    def __eq__(self,other):
        if not isinstance(other,MetaData):
            return False
        
        if other.fromData() == self.fromData() and \
           other.toData() == self.toData() and \
           type(other.relationship) == type(self.relationship):
            return True
        return False
    def __ne__(self,other):
        return not self.__eq__(other)

    def fromData(self):
        return self._from
    def toData(self):
        return self._to
    def relationship(self):
        return self._relationship
    def date(self):
        return time.localtime(self._time)
    def formatDate(self,fmt=None):
        if not hasattr(self,'_moment'):
            self._moment = activist.Time.Moment(self._time)
        return self._moment.format(fmt)


    ## this stuff all needs re-thinking and should really come ##
    ## from Dublin core namespaces wherever possible ##

    class Relationship(object):
        __metaclass__=TypeStringFudger('relationship')
        def __str__(self): return 'relationship'

    class Creator(Relationship):
        __metaclass__=TypeStringFudger('DC.Creator')
        def __str__(self): return 'DC.Creator'
    
    class Author(Relationship):
        __metaclass__=TypeStringFudger('DC.Author')
        def __str__(self): return 'DC.Author'

    class Title(Relationship):
        __metaclass__=TypeStringFudger('DC.Title')
        def __str__(self): return 'DC.Title'

                                        # how are these DC.Relation
                                        # things supposed to work? I'm
                                        # just guessing here...

    class BelongsWith(Relationship):
        __metaclass__=TypeStringFudger('DC.Relation(belongs-with)')
        def __str__(self): return 'DC.Relation(belongs-with)'
    class InformationOf(Relationship):
        __metaclass__=TypeStringFudger('DC.Relation(information-of)')
        def __str__(self): return 'DC.Relation(information-of)'
    class LocatedAt(Relationship):
        __metaclass__=TypeStringFudger('DC.Relation(located-at)')
        def __str__(self): return 'DC.Relation(located-at)'
    class CommentOn(Relationship):
        __metaclass__=TypeStringFudger('DC.Relation(comment-on)')
        def __str__(self): return 'DC.Relation(comment-on)'
    class Message(Relationship):
        __metaclass__=TypeStringFudger('DC.Relation(message)')
        def __str__(self): return 'DC.Relation(message)'


# FIXME TODO something weird with pickling of metadata; thinks these should be
# module-level...
BelongsWith = MetaData.BelongsWith
InformationOf = MetaData.InformationOf
LocatedAt = MetaData.LocatedAt
CommentOn = MetaData.CommentOn
Message = MetaData.Message

Relationship = MetaData.Relationship
Creator = MetaData.Creator
Title = MetaData.Title

__all__ = ["MetaData","MetaDataFactory"]
